package com.ubxtech.web.config;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ubxtech.core.context.ContextConstants;
import com.ubxtech.core.plugins.RequestMappingScanService;
import com.ubxtech.core.utils.SpringUtils;
import com.ubxtech.web.handler.auth.AuthCollectService;
import com.ubxtech.web.handler.auth.LoginAppSecretService;
import com.ubxtech.web.handler.auth.LoginAuthService;
import com.ubxtech.web.handler.auth.impl.*;
import com.ubxtech.web.locale.CustomLocaleDateModule;
import com.ubxtech.web.locale.CustomLocaleResolver;
import com.ubxtech.web.properties.AuthProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import java.util.TimeZone;
import java.util.concurrent.*;

/**
 * @author Victor.Xiao
 * @since 2022-01-07 17:46
 **/
@Slf4j
@Configuration
@EnableConfigurationProperties(AuthProperties.class)
@EnableAsync
public class BaseConfig {

    @Resource
    private AuthProperties authProperties;


    /**
     * Spring 工具类
     *
     * @param applicationContext 上下文
     */
    @Bean
    public SpringUtils getSpringUtils(ApplicationContext applicationContext) {
        SpringUtils instance = SpringUtils.getInstance();
        SpringUtils.setApplicationContext(applicationContext);
        return instance;
    }

    @Bean
    @ConditionalOnMissingBean
    public LoginAppSecretService getLoginAppSecretService(){
        return new LoginAppSecretServiceImpl( authProperties.getAppSecretsStr(),authProperties.getCheckFlag());
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = AuthProperties.PREFIX, name = "type", havingValue = "cloud")
    public LoginAuthService getLoginAuthService(AuthCollectService authCollectService){
        return new CloudAuthServiceImpl(authCollectService);
    }


    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = AuthProperties.PREFIX, name = "type", havingValue = "sign")
    public LoginAuthService getSignAuthService(AuthCollectService authCollectService ){
        return new SignAuthServiceImpl(authCollectService);
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = AuthProperties.PREFIX, name = "type", havingValue = "none" ,matchIfMissing = true)
    public LoginAuthService getNoneAuthService(){
        return new NoneAuthServiceImpl();
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = AuthProperties.PREFIX, name = "type", havingValue = "express")
    public LoginAuthService getExpressAuthService(AuthCollectService authCollectService){
        return new ExpressAuthServiceImpl(authCollectService,authProperties);
    }
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = AuthProperties.PREFIX, name = "type", havingValue = "local")
    public LoginAuthService getLocalAuthService(AuthCollectService authCollectService){
        return new LocalAuthServiceImpl(authCollectService);
    }

    @Bean
    @ConditionalOnProperty(prefix = AuthProperties.PREFIX, name = "accessOrigin", havingValue = "1")
    public WebMvcConfigurer corsConfig()
    {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").
                        allowedOriginPatterns("*"). //允许跨域的域名，可以用*表示允许任何域名使用
                        allowedMethods("GET", "POST", "PUT", "DELETE","PATCH","OPTIONS"). //允许任何方法（post、get等）
                        allowedHeaders("Origin",
                        "No-Cache",
                        "X-Requested-With",
                        "If-Modified-Since", "Pragma",
                        "Last-Modified", "Cache-Control", "Expires",  "Content-Type"
                        ,ContextConstants.USER_ID
                        ,ContextConstants.TOKEN
                        ,ContextConstants.USERNAME
                        ,ContextConstants.NICKNAME
                        ,ContextConstants.HEADER_REAL_IP
                        ,ContextConstants.HEADER_FORWARDED_FOR
                        ,ContextConstants.X_TRACE_ID
                        ,ContextConstants.P_TRACE_ID
                        ,ContextConstants.TRACE_ID
                        ). //允许任何请求头
                        allowCredentials(true). //带上cookie信息
                        exposedHeaders(HttpHeaders.SET_COOKIE).maxAge(3600L); //maxAge(3600)表明在3600秒内，不需要再发送预检验请求，可以缓存该结果
            }
        };
    }


    @Bean
    public LocaleResolver localeResolver() {
        return new CustomLocaleResolver(authProperties);
    }


    /**
     * 主要是实现动态转化时区,根据前端传的lang或者用户所在时区来转化对应TimeZone的时间
     * 可能需要AutoConfigureBefore(JacksonAutoConfiguration.class)
     * https://blog.csdn.net/weixin_42645678/article/details/129186806
     * @author Victor.Xiao
     * @since 2023-09-15 17:30
    */
    @Bean
    @ConditionalOnClass(ObjectMapper.class)
    public Jackson2ObjectMapperBuilderCustomizer mappingJackson2HttpMessageConverter2() {
        if (!"dynamic".equalsIgnoreCase(authProperties.getLocale())) {
            return builder -> {};
        }
        return builder -> builder.modules(new CustomLocaleDateModule(authProperties));
    }


    @Bean
    @ConditionalOnMissingBean
    public RequestMappingScanService requestMappingScanService(SpringUtils springUtils){
        return (requestMappingServiceDTO) -> {

        };
    }


    /**
     * 主要用于任务调度 可以按照时间间隔或Cron表达式执行任务
     * @author Victor.Xiao
     * @since 2023-11-24 16:37
     * @return org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
    */
    @Bean
    @ConditionalOnMissingBean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        // 线程池大小
        threadPoolTaskScheduler.setPoolSize(100);
        // 线程名称
        threadPoolTaskScheduler.setThreadNamePrefix("local-thread-pool-");
        // 等待时长
        threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
        // 调度器shutdown被调用时等待当前被调度的任务完成
        threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        return threadPoolTaskScheduler;
    }

    /**
     * 主要用于线程池管理，执行异步任务和多线程任务。自定义异步线程池,得命名为taskExecutor
     * @author Victor.Xiao
     * @since 2023-11-24 16:38
     * @return org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
    */
    @Bean(name = "taskExecutor")
    @ConditionalOnMissingBean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置线程池核心容量
        executor.setCorePoolSize(4);
        // 设置线程池最大容量
        executor.setMaxPoolSize(8);
        // 设置任务队列长度
        executor.setQueueCapacity(200);
        // 设置线程超时时间
        executor.setKeepAliveSeconds(60);
        // 设置线程名称前缀
        executor.setThreadNamePrefix("local-async-pool-");
        // 设置任务丢弃后的处理策略,当poolSize已达到maxPoolSize，如何处理新任务（是拒绝还是交由其它线程处理）,CallerRunsPolicy：不在新线程中执行任务，而是由调用者所在的线程来执
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    @PostConstruct
    private void init() {
        if (StrUtil.isNotBlank(authProperties.getSysTimeZone())) {
            //GMT  GMT+8
            System.setProperty("user.timezone", authProperties.getSysTimeZone());
            TimeZone.setDefault(TimeZone.getTimeZone(authProperties.getSysTimeZone()));
        }
    }

}
